package com.aibaixun.system.utils;

import com.google.common.collect.Lists;
import okhttp3.*;
import okio.Buffer;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class EvsPostUtils {

    public static void main(String[] args) {
        String method = "GET";
        String url = "https://evs.bj.baidubce.com/v1/device/1061357";
        String ak = "ALTAKn9KUT2rqPu6ycC7DP0Gn8";
        String sk = "3e0c6c3355b846378dc4d254c282c8b4";

        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .build();

        Request request = generateAuthorizedRequest(method, url, ak, sk);

        String curlCommand = buildCurlCommand(request);

        try (Response response = client.newCall(request).execute()) {
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static String buildCurlCommand(Request request) {
        Map<String, String> headers = request.headers().toMultimap().entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, x -> x.getValue().get(0)));
        return buildCurlCommand(request.method(), request.url().toString(), headers, readRequestData(request));
    }

    public static String buildCurlCommand(String httpMethod, String url, Map<String, String> headers, String body) {
        Set<String> excludeHeaders = new HashSet<>(Lists.newArrayList("Content-Length", "Connection", "Accept-Encoding", "User-Agent", "Accept"));
        StringBuilder curlCommand = new StringBuilder("curl -i --max-time 10");

        if (!"GET".equalsIgnoreCase(httpMethod)) {
            curlCommand.append(" -X ").append(httpMethod);
        }

        curlCommand.append(" '").append(url).append("'");

        for (Map.Entry<String, String> entry : headers.entrySet()) {
            String name = entry.getKey();
            String value = entry.getValue();

            if (excludeHeaders.contains(name)) {
                continue;
            }

            curlCommand.append(" -H '").append(name).append(": ").append(value).append("'");
        }

        if (body != null && body.length() > 0) {
            curlCommand.append(" -d '").append(body).append("'");
        }

        return curlCommand.toString();
    }


    public static Request generateAuthorizedRequest(String method, String url, String ak, String sk) {
        String xBceDate = convertTimestampToUTCString(System.currentTimeMillis());

        HttpUrl httpUrl = HttpUrl.parse(url);
        String query = httpUrl.query();
        String uri = httpUrl.encodedPath();
        String host = httpUrl.host();

        Map<String, String> headers = new HashMap<>();
        headers.put("Host", host);
        headers.put("x-bce-date", xBceDate);

        String authStringPrefix = String.format("bce-auth-v1/%s/%s/1800", ak, xBceDate);

        // Canonical Query String
        String canonicalQuery = genCanonicalQueryString(query);

        // Canonical Headers
        Map<String, String> canonicalHeaders = canonicalHeadersFilter(headers);
        String signedHeaders = canonicalHeaders.keySet().stream().sorted().collect(Collectors.joining(";"));
        String canonicalHeadersStr = canonicalHeaders.entrySet().stream()
                .sorted(Comparator.comparing(Map.Entry::getKey))
                .map(entry -> entry.getKey() + ":" + URLEncoder.encode(entry.getValue())).
                collect(Collectors.joining("\n"));
//        String canonicalHeadersStr = "host:127.0.0.1;x-bce-date:" + xBceDate;

        // Canonical Request
        String canonicalRequest = method + "\n" + uri + "\n" + canonicalQuery + "\n" + canonicalHeadersStr;

        // Signing Key
        String signingKey = byteToHex(hmacSHA256(sk, authStringPrefix));

        // Signature
        byte[] signature = hmacSHA256(signingKey, canonicalRequest);

        String authorization = authStringPrefix + "/" + signedHeaders + "/" + byteToHex(signature);

        headers.put("Authorization", authorization);

        return new Request.Builder()
                .url(url)
                .method(method, null)
                .headers(Headers.of(headers))
                .build();
    }

    public static String genCanonicalQueryString(String query) {
        if (query == null) {
            return "";
        }

        List<String> params = new ArrayList<>();
        for (String param : query.split("&")) {
            String[] keyValue = param.split("=");
            if (keyValue.length == 2) {
                params.add(keyValue[0] + "=" + keyValue[1]);
            } else {
                params.add(keyValue[0] + "=");
            }
        }

        params.sort(String::compareTo);
        return String.join("&", params);
    }

    public static Map<String, String> canonicalHeadersFilter(Map<String, String> headers) {
        Map<String, String> filteredHeaders = new HashMap<>();
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            String key = entry.getKey().toLowerCase();
            String value = entry.getValue();
            if ("host".equals(key) || key.startsWith("x-bce-")) {
                filteredHeaders.put(key, value);
            }
        }
        return filteredHeaders;
    }

    public static byte[] hmacSHA256(String key, String data) {
        try {
            Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            hmacSHA256.init(secretKey);
            return hmacSHA256.doFinal(data.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            e.printStackTrace();
            return new byte[0];
        }
    }

    public static byte[] hmacSHA256(byte[] key, String data) {
        try {
            Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(key, "HmacSHA256");
            hmacSHA256.init(secretKey);
            return hmacSHA256.doFinal(data.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            e.printStackTrace();
            return new byte[0];
        }
    }

    public static String byteToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }

    public static String convertTimestampToUTCString(long timestampMillis) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        format.setTimeZone(TimeZone.getTimeZone("GMT+0"));
        return format.format(new Date(timestampMillis));
    }


    private static String readRequestData(Request request) {
        try {
            RequestBody requestBody = request.body();
            if (requestBody != null) {
                Buffer buffer = new Buffer();
                requestBody.writeTo(buffer);
                return buffer.readUtf8();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return "";
    }

}
